#pragma once

/// @file userver/clients/http/client_core.hpp
/// @brief @copybrief clients::http::ClientCore

#ifdef USERVER_TVM2_HTTP_CLIENT
#error Use clients::Http from clients/http.hpp instead
#endif

#include <memory>

#include <userver/moodycamel/concurrentqueue_fwd.h>

#include <userver/clients/dns/resolver_fwd.hpp>
#include <userver/clients/http/client.hpp>
#include <userver/clients/http/config.hpp>
#include <userver/clients/http/request.hpp>
#include <userver/engine/task/task_processor_fwd.hpp>
#include <userver/rcu/rcu.hpp>
#include <userver/utils/fast_pimpl.hpp>
#include <userver/utils/impl/internal_tag.hpp>
#include <userver/utils/not_null.hpp>
#include <userver/utils/periodic_task.hpp>
#include <userver/utils/swappingsmart.hpp>
#include <userver/yaml_config/fwd.hpp>

USERVER_NAMESPACE_BEGIN

namespace tracing {
class TracingManagerBase;
}  // namespace tracing

namespace curl {
class easy;
class multi;
class ConnectRateLimiter;
}  // namespace curl

namespace engine::ev {
class ThreadPool;
}  // namespace engine::ev

namespace clients::http {
namespace impl {
class EasyWrapper;
}  // namespace impl

struct TestsuiteConfig;
class Statistics;
struct PoolStatistics;
struct InstanceStatistics;
class DestinationStatistics;

/// @ingroup userver_clients
///
/// @brief HTTP client that returns a HTTP request builder from
/// CreateRequest().
///
/// Usually retrieved from @ref components::HttpClientCore component.
///
/// ## Example usage:
///
/// @snippet clients/http/client_test.cpp  Sample HTTP Client usage
class ClientCore final : public Client {
public:
    /// @cond
    // For internal use only
    ClientCore(utils::impl::InternalTag, ClientSettings settings, engine::TaskProcessor& fs_task_processor);
    /// @endcond

    ~ClientCore() override;

    /// @brief Returns a HTTP request builder type with preset values of
    /// User-Agent and some of the Testsuite stuff (if any).
    ///
    /// @note This method does not apply plugins, they could be applied
    /// manually using @ref clients::http::Request::SetPluginsList()
    /// @note This method is thread-safe despite being non-const.
    Request CreateRequest() override;

    /// @cond
    // For internal use only.
    void SetMultiplexingEnabled(bool enabled);

    // For internal use only.
    void SetMaxHostConnections(size_t max_host_connections);

    // For internal use only.
    PoolStatistics GetPoolStatistics() const;

    // Set max number of automatically created destination metrics.
    // For internal use only.
    void SetDestinationMetricsAutoMaxSize(size_t max_size);

    // For internal use only.
    const http::DestinationStatistics& GetDestinationStatistics() const;

    // For internal use only.
    void SetTestsuiteConfig(TestsuiteConfig&& config);

    // For internal use only.
    void SetAllowedUrlsExtra(std::vector<std::string>&& urls);

    // For internal use only.
    void SetConfig(const impl::Config&);

    // For internal use only.
    void ResetUserAgent(std::optional<std::string> user_agent = std::nullopt);

    // For internal use only.
    void SetDnsResolver(clients::dns::Resolver* resolver);
    /// @endcond

private:
    void ReinitEasy();

    InstanceStatistics GetMultiStatistics(size_t n) const;

    size_t FindMultiIndex(const curl::multi*) const;

    // Functions for EasyWrapper that must be noexcept, as they are called from
    // the EasyWrapper destructor.
    friend class impl::EasyWrapper;
    void IncPending() noexcept { ++pending_tasks_; }
    void DecPending() noexcept { --pending_tasks_; }
    void PushIdleEasy(std::shared_ptr<curl::easy>&& easy) noexcept;

    std::shared_ptr<curl::easy> TryDequeueIdle() noexcept;

    std::atomic<std::size_t> pending_tasks_{0};

    const DeadlinePropagationConfig deadline_propagation_config_;
    CancellationPolicy cancellation_policy_;

    std::shared_ptr<DestinationStatistics> destination_statistics_;
    std::unique_ptr<engine::ev::ThreadPool> thread_pool_;
    std::vector<Statistics> statistics_;
    std::vector<std::unique_ptr<curl::multi>> multis_;

    static constexpr size_t kIdleQueueSize = 616;
    static constexpr size_t kIdleQueueAlignment = 8;
    using IdleQueueTraits = moodycamel::ConcurrentQueueDefaultTraits;
    using IdleQueueValue = std::shared_ptr<curl::easy>;
    using IdleQueue = moodycamel::ConcurrentQueue<IdleQueueValue, IdleQueueTraits>;
    utils::FastPimpl<IdleQueue, kIdleQueueSize, kIdleQueueAlignment> idle_queue_;

    engine::TaskProcessor& fs_task_processor_;
    std::optional<std::string> user_agent_;

    utils::SwappingSmart<const curl::easy> easy_;
    utils::PeriodicTask easy_reinit_task_;

    // Testsuite support
    std::shared_ptr<const TestsuiteConfig> testsuite_config_;
    rcu::Variable<std::vector<std::string>> allowed_urls_extra_;

    std::shared_ptr<curl::ConnectRateLimiter> connect_rate_limiter_;

    clients::dns::Resolver* resolver_{nullptr};
    utils::NotNull<const tracing::TracingManagerBase*> tracing_manager_;
};

}  // namespace clients::http

USERVER_NAMESPACE_END
